home *** CD-ROM | disk | FTP | other *** search
Text File | 1996-02-19 | 16.2 KB | 447 lines | [TEXT/CWIE] |
- //================================================================================
- // Greg Anderson
- // db+
- //
- // Cursor to a data record
- // 19 May 1995
- //================================================================================
- #pragma once
-
- #include "DataRecord.h"
-
- #include "DBProperty.h"
- #include "GroupControlObject.h"
- #include "DatabaseDocument.h"
- #include "Transaction.h"
-
- //
- // For CopyMemory
- //
- #include "AbstractData.h"
-
- #include "Exceptions.h"
-
- #define INHERITED TAbstractRecord
-
-
- //--------------------------------------------------------------------------------
- // TDataRecord::~TDataRecord
- //--------------------------------------------------------------------------------
- TDataRecord::~TDataRecord()
- {
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::PrepareToCommit
- //--------------------------------------------------------------------------------
- void TDataRecord::PrepareToCommit(TTransaction* t)
- {
- //
- // If our owner freed us, then mark this record as
- // unused so that it will be pushed onto the free
- // list when the transaction changes are committed.
- //
- if(fDataReleased == true)
- {
- this->FreeThisRecord(t);
- }
- //
- // Check to see if the physical size of our data block
- // has changed, and if so, allocate a new one.
- //
- // Remember, we need to be able to back out
- // the changes made here, just in case another
- // object fails in PrepareToCommit. Fortunately,
- // this happens automatically, as the relocated
- // data block is simply added to this transaction.
- //
- // n.b. The relocated data will also be given
- // a chance to prepare to commit, but it should
- // discover that its size has not changed, and
- // therefore it will not try to relocate itself
- // again.
- //
- else
- {
- //
- // How big did we use to be, and how big are we now?
- //
- long currentEncodedPhysicalSize = this->GroupControlObject()->BlockEncodedPhysicalSize(this->RecordIndex());
- long desiredEncodedPhysicalSize = this->BlockEncodedPhysicalSize(t);
- long desiredSizeDelta = desiredEncodedPhysicalSize - currentEncodedPhysicalSize;
-
- //
- // If we are _increasing_ in size, or if we are _decreasing_
- // by more than one record in size, then we want to realocate.
- // (We could also realocate if we decrease by only one
- // record, but note then that the free list (kLastDataBlockFreeList - 1)
- // is never used.)
- //
- if((desiredSizeDelta > 0) || (desiredSizeDelta < -1))
- {
- //
- // Make a new data record to store our data in.
- // This record will become part of the transaction,
- // so it will be backed out or committed along with
- // all of the other changes.
- //
- // Note that this new object is added to the beginning
- // of the transaction list, so it will not have a chance
- // to prepare to commit (actually, the code is written
- // under the assumption that PrepareToCommit may or may
- // not be called for the new object).
- //
- AnUpdate<TDataRecord> relocatedData = this->DBDocument()->NewDataRecord(t, this->GetDataLength(t));
- relocatedData->SetDataOwner(t, this->Transaction()->GetDBPropertyUpdatePointer(fDataOwner));
- fDataOwner = AConst<TDBProperty>(nil);
-
- //
- // Require that our data will fit inside the new
- // record, then copy it over.
- //
- Require(relocatedData->MaximumDataLength(t) >= this->GetDataLength(t));
- relocatedData->SetTypedData(t, this->DataReference(t));
-
- //
- // Now that we've copied our data over, we must
- // release our data. We'd better put our physical
- // size back to what it was, or we'll may have problems
- // when the free record is committed
- //
- // Everything will come back to the way it was if the transaction
- // changes are discarded, though.
- //
- this->SetBlockEncodedPhysicalSize(t, currentEncodedPhysicalSize);
- this->FreeThisRecord(t);
- }
- }
-
- INHERITED::PrepareToCommit(t);
- } // TDataRecord::PrepareToCommit
-
- //--------------------------------------------------------------------------------
- // TDataRecord::CommitChanges
- //--------------------------------------------------------------------------------
- void TDataRecord::CommitChanges(TTransaction* t)
- {
- //
- // PrepareToCommit should have made sure that our
- // block size is exactly correct, but in some instances
- // we may need to nudge it a bit
- //
- long currentEncodedPhysicalSize = this->GroupControlObject()->BlockEncodedPhysicalSize(this->RecordIndex());
- long desiredEncodedPhysicalSize = this->BlockEncodedPhysicalSize(t);
-
- //
- // n.b we can't fail in CommitChanges, so we'd better try to
- // make the data fit somehow, some way
- //
- if(currentEncodedPhysicalSize != desiredEncodedPhysicalSize)
- {
- //
- // Big problems will result if our encoded size is wrong
- //
- this->SetBlockEncodedPhysicalSize(t, currentEncodedPhysicalSize);
-
- //
- // If we made the block bigger, make our size correction
- // bigger so that our logical size doesn't change.
- // If we made our block SMALLER (bad news!), then we
- // just clipped some of our data off. Make our size
- // correction zero to minimize the loss (this should
- // never happen)
- //
- long newSizeCorrection = this->SizeCorrection(t) + ((currentEncodedPhysicalSize - desiredEncodedPhysicalSize) * kSingleRecordSize);
- if(newSizeCorrection < 0)
- {
- ASSERT(false);
- newSizeCorrection = 0;
- }
- this->SetSizeCorrection(t, newSizeCorrection);
- }
-
- //
- // Now that our block is the same size as the slot we'd
- // like to copy it into, go ahead and move it in.
- //
- INHERITED::CommitChanges(t);
- } // TDataRecord::CommitChanges
-
- //--------------------------------------------------------------------------------
- // TDataRecord::ReleaseData
- //
- // We don't actually convert this record into a free node until it's time
- // to commit; that way, we don't need to worry about un-freeing the node or
- // allocating a new one if the same property record wants to store more
- // external data in us even after we were freed.
- //--------------------------------------------------------------------------------
- void TDataRecord::ReleaseData(TTransaction*)
- {
- fDataReleased = true;
- } // TDataRecord::ReleaseData
-
- //--------------------------------------------------------------------------------
- // TDataRecord::GetDataType
- //
- // External data records don't know their data type--they have to ask
- // their owner.
- //--------------------------------------------------------------------------------
- long TDataRecord::GetDataType(TTransaction* t) const
- {
- if(fDataOwner.Exists())
- return fDataOwner->GetDataType(t);
- else
- return 0;
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::GetDataLength
- //
- // External data records do know their length, though; it's stored as a
- // size-correction off of the block's physical size
- //--------------------------------------------------------------------------------
- long TDataRecord::GetDataLength(TTransaction* t) const
- {
- return this->MaximumDataLength(t) - this->SizeCorrection(t);
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::BlockEncodedPhysicalSize
- //--------------------------------------------------------------------------------
- long TDataRecord::BlockEncodedPhysicalSize(TTransaction* t) const
- {
- return (this->GetRecordData(t, kDataFlags) & kDataRecordPhysicalSizeBits) >> kDataRecordPhysicalSizeShift;
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::BlockPhysicalSize
- //--------------------------------------------------------------------------------
- long TDataRecord::BlockPhysicalSize(TTransaction* t) const
- {
- return ((this->BlockEncodedPhysicalSize(t) + 1) * (kSingleRecordSize));
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::MaximumDataLength
- //--------------------------------------------------------------------------------
- long TDataRecord::MaximumDataLength(TTransaction* t) const
- {
- //
- // We have enough room to store 8 bytes less than our physical size
- // (since our header is two longwords in size)
- //
- return this->BlockPhysicalSize(t) - kHeaderSize;
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::SizeCorrection
- //--------------------------------------------------------------------------------
- long TDataRecord::SizeCorrection(TTransaction* t) const
- {
- return (this->GetRecordData(t, kDataFlags) & kDataBlockSizeCorrection) >> kDataBlockSizeCorrectionShift;
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::SetSizeCorrection
- //--------------------------------------------------------------------------------
- void TDataRecord::SetSizeCorrection(TTransaction* t, long sizeCorrection)
- {
- long oldFlagValue = this->GetRecordData(t, kDataFlags);
- long newFlagValue = (oldFlagValue & ~kDataBlockSizeCorrection) + (sizeCorrection << kDataBlockSizeCorrectionShift);
-
- this->ChangeRecordData(t, kDataFlags, newFlagValue);
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::SetBlockEncodedPhysicalSize
- //--------------------------------------------------------------------------------
- void TDataRecord::SetBlockEncodedPhysicalSize(TTransaction* t, long blockPhysicalSize)
- {
- long oldFlagValue = this->GetRecordData(t, kDataFlags);
- long newFlagValue = (oldFlagValue & ~kDataRecordPhysicalSizeBits) + (blockPhysicalSize << kDataRecordPhysicalSizeShift);
-
- this->ChangeRecordData(t, kDataFlags, newFlagValue);
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::SetLogicalSize
- //--------------------------------------------------------------------------------
- void TDataRecord::SetLogicalSize(TTransaction* t, long logicalSize)
- {
- long sizeCorrection = this->MaximumDataLength(t) - logicalSize;
- Require((sizeCorrection >= 0) && (logicalSize >= 0));
-
- //
- // If the size correction is too large, then shrink the
- // physical size of this block down some
- //
- if(sizeCorrection >= kSingleRecordSize)
- {
- long decreaseEncodedSize = sizeCorrection / (kSingleRecordSize);
- sizeCorrection -= (decreaseEncodedSize * (kSingleRecordSize));
- this->SetBlockEncodedPhysicalSize(t, this->BlockEncodedPhysicalSize(t) - decreaseEncodedSize);
- }
- this->SetSizeCorrection(t, sizeCorrection);
- ASSERT(logicalSize == this->GetDataLength(t));
- } // TDataRecord::SetLogicalSize
-
- //--------------------------------------------------------------------------------
- // TDataRecord::GetTypedData
- //--------------------------------------------------------------------------------
- void TDataRecord::GetTypedData(TTransaction* t, TAbstractDataReference& data) const
- {
- data.CopyFrom(this->DataReference(t));
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::Verify
- //--------------------------------------------------------------------------------
- void TDataRecord::Verify(TTransaction*, Boolean /*verifyDeep = false*/) const
- {
- //
- // Ask the group control object to verify the next/previous links
- // of this data record
- //
- this->GroupControlObject()->Verify(this->RecordIndex(), false);
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::SetDataOwner
- //--------------------------------------------------------------------------------
- void TDataRecord::SetDataOwner(TTransaction* t, AnUpdate<TDBProperty> dataOwner)
- {
- fDataOwner = dataOwner;
- if(dataOwner.Exists())
- {
- this->ChangeRecordData(t, kDataOwnerReference, dataOwner->RecordIndex());
- dataOwner->SetExternalDataIndex(t, this->RecordIndex());
- }
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::InitializeNewDataRecord
- //--------------------------------------------------------------------------------
- void TDataRecord::InitializeNewDataRecord(TTransaction* t)
- {
- long oldFlagValue = this->GetRecordData(t, kDataFlags);
- long newFlagValue = oldFlagValue & kInitialDataBlockFlagsMask;
-
- this->ChangeRecordData(t, kDataFlags, newFlagValue);
- this->ChangeRecordData(t, kDataOwnerReference, kNilIndex);
- } // TDataRecord::InitializeNewDataRecord
-
- //--------------------------------------------------------------------------------
- // TDataRecord::SetTypedData
- //
- // This is the key routine--we need to handle our data block growing larger
- // than we have room to store (faked out by making the change image larger,
- // and then allocating a different data record when it's time to commit) or
- // smaller than we'd like to keep around (handled similarly).
- //--------------------------------------------------------------------------------
- void TDataRecord::SetTypedData(TTransaction* t, const TAbstractDataReference& data)
- {
- //
- // If our data is released by our owner at one point, and
- // new data is given to us later, then set fDataReleased
- // to false
- //
- if(fDataReleased == true)
- {
- fDataReleased = false;
-
- //
- // This work is probably redundant with a step done
- // later.
- //
- if(fDataOwner.Exists())
- this->Transaction()->GetDBPropertyUpdatePointer(fDataOwner)->SetExternalDataIndex(t, this->RecordIndex());
- }
-
- //
- // If we don't have a change image yet, then make one
- //
- if(fChangeImage == nil)
- {
- fChangeImage = this->GroupControlObject()->MakeRecordDataCopy(this->RecordIndex());
- fChangeImageUnchanged = true;
- fChangeImageMayHaveChangedBack = false;
- }
-
- //
- // It is possible that TAbstractRecord::ChangeRecordData
- // may have created the change image, so we need to initialize
- // the change image length variable independantly of
- // the creation of the change image, above.
- //
- if(fChangeImageSize == 0)
- fChangeImageSize = this->BlockPhysicalSize(t);
-
- //
- // Calculate the minimum size required to fit
- // the provided data into a data record (i.e.,
- // round up the data + headersize to the nearest 32-byte
- // boundary)
- //
- long minEncodedSize = (((data.DataLength() + kHeaderSize) - 1) / (kSingleRecordSize));
- long minLength = (minEncodedSize + 1) * (kSingleRecordSize);
-
- //
- // If the current size of the data record is too small to
- // hold the new data, then ask the group control object
- // to make our change image bigger
- //
- if(minLength > fChangeImageSize)
- {
- long* newChangeImage = new long[minLength / sizeof(long)];
- CopyMemory(fChangeImage, newChangeImage, fChangeImageSize); // memcpy(newChangeImage, fChangeImage, fChangeImageSize);
- delete [] fChangeImage;
- fChangeImage = newChangeImage;
- fChangeImageSize = minLength;
- }
-
- //
- // Set the block encoded physical size to be the
- // smallest value that is >= what is needed to
- // store the data and header. If the length of
- // our data is shrinking, we will let the change
- // image remain in its too-large state until the
- // transaction changes are committed or discarded.
- //
- // Note that it is somewhat bogus to ever change
- // our encoded block size, since the group control
- // object will not allow us to grow larger or shrink
- // smaller. However, we will notice the change in
- // size immediately before doing a commit (in
- // PrepareToCommit, above) and copy the data somewhere
- // else. Of course, if changes are discarded it does
- // not matter what we set our length to, since
- // everything will go back to being the way it was
- // regardless.
- //
- this->SetBlockEncodedPhysicalSize(t, minEncodedSize);
-
- //
- // Copy the new data into the change image, and set the
- // cached value of the current length to an appropriate
- // number (The data length may shrink beyond our ability
- // to represent it with the size correction, or grow beyond
- // the physical size of this block. Adjusting the actual
- // size of the record is not done until the transaction
- // changes are committed.)
- //
- TUpdataDataReference updateChangeImage(this->GetDataType(t), (char*) &fChangeImage[kDataStorageStart], this->GetDataLength(t), this->MaximumDataLength(t));
- updateChangeImage.CopyFrom(data);
- this->SetLogicalSize(t, updateChangeImage.DataLength());
- fChangeImageUnchanged = false;
- fChangeImageMayHaveChangedBack = true;
- }
-
- //--------------------------------------------------------------------------------
- // TDataRecord::DataReference
- //--------------------------------------------------------------------------------
- TConstDataReference TDataRecord::DataReference(TTransaction* t) const
- {
- return this->RecordDataReference(t, this->GetDataType(t), kDataStorageStart, this->GetDataLength(t));
- }
-
-